#include "WM8960.h"
#include "Play_Music.h"
#include "i2c.h"


/*
    WM8960声卡驱动
*/


// 频率设置
const uint16_t I2S_PSC[][5] = 
{
    {800,256,5,12,1},   // 8khz
    {1102,429,4,19,0},  // 11.025khz
    {1600,213,2,13,0},  // 16khz
    {2205,429,4,9,1},   // 22.05khz
    {3200,213,2,6,1},   // 32khz
    {4410,271,2,6,0},   // 44.1khz
    {4800,258,3,3,1},   // 48khz
    {9600,344,2,3,1},   // 96khz
    {19200,393,2,2,0},  // 192khz
};


//resgister value
static uint16_t WM8960_REG_VAL[56] =
{  
  0x0097, 0x0097, 0x0000, 0x0000, 0x0000, 0x0008, 0x0000, 0x000A,
  0x01C0, 0x0000, 0x00FF, 0x00FF, 0x0000, 0x0000, 0x0000, 0x0000,
  0x0000, 0x007B, 0x0100, 0x0032, 0x0000, 0x00C3, 0x00C3, 0x01C0,
  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
  0x0100, 0x0100, 0x0050, 0x0050, 0x0050, 0x0050, 0x0000, 0x0000,
  0x0000, 0x0000, 0x0040, 0x0000, 0x0000, 0x0050, 0x0050, 0x0000,
  0x0000, 0x0037, 0x004D, 0x0080, 0x0008, 0x0031, 0x0026, 0x00ED
};


// 初始化WM8960
// 返回0初始化成功
uint8_t WM8960_Init(void)
{
    uint8_t res;
    i2c_init();
    WM8960_I2S_GPIO_Config();  //I2S IO配置

    //Reset Device
    res = WM8960_Write_Reg(0x0f, 0x0000);
    if(res != 2)
        return res;
    else
        printf("WM8960 reset completed !!\r\n");

    //Set Power Source
    res =  WM8960_Write_Reg(0x19, 1<<8 | 1<<7 | 1<<6);
    res += WM8960_Write_Reg(0x1A, 1<<8 | 1<<7 | 1<<6 | 1<<5 | 1<<4 | 1<<3);
    res += WM8960_Write_Reg(0x2F, 1<<3 | 1<<2);
    if(res != 6)  {
        printf("Source set fail !!\r\n");
        printf("Error code: %d\r\n",res);
        return res;
    }
    
    //Configure clock
    //MCLK->div1->SYSCLK->DAC/ADC sample Freq = 25MHz(MCLK)/2*256 = 48.8kHz
    WM8960_Write_Reg(0x04, 0x0090);
    
    //Configure ADC/DAC
    WM8960_Write_Reg(0x05, 0x0000);
    
    //Configure audio interface
    //I2S format 16 bits word length
    WM8960_Write_Reg(0x07, 0x0002);
    
    //Configure HP_L and HP_R OUTPUTS
    WM8960_Write_Reg(0x02, 0x0062 | 0x0100);  //LOUT1 Volume Set
    WM8960_Write_Reg(0x03, 0x0062 | 0x0100);  //ROUT1 Volume Set
    
    //Configure SPK_RP and SPK_RN
    WM8960_Write_Reg(0x28, 0x006F | 0x0100); //Left Speaker Volume
    WM8960_Write_Reg(0x29, 0x006F | 0x0100); //Right Speaker Volume
    
    //Enable the OUTPUTS
    WM8960_Write_Reg(0x31, 0x00F7); //Enable Class D Speaker Outputs
    
    //Configure DAC volume
    WM8960_Write_Reg(0x0a, 0x00F0 | 0x0100);
    WM8960_Write_Reg(0x0b, 0x00F0 | 0x0100);
    
    //3D
    //  WM8960_Write_Reg(0x10, 0x001F);
    
    //Configure MIXER
    WM8960_Write_Reg(0x22, 1<<8 | 1<<7 | 4 << 4);
    WM8960_Write_Reg(0x25, 1<<8 | 1<<7 | 4 << 4);
    
    //Jack Detect
    WM8960_Write_Reg(0x18, 1<<6 | 0<<5);
    WM8960_Write_Reg(0x17, 0x01C3);
    WM8960_Write_Reg(0x30, 0x0009);//0x000D,0x0005 


    //音频采样速率\通讯标准配置
    WM8960_I2Sx_Mode_Config(I2S_Standard_Phillips,I2S_DataFormat_16b,
                            I2S_AudioFreq_44k);  
    WM8960_I2Sx_TX_DMA_Init();
    return 0;
}

// 配置DMA传输数据
void WM8960_I2S_DMA_Config(uint16_t *buffer,uint16_t size)
{
    DMA_Cmd(WM8960_I2Sx_TX_DMA_STREAM,DISABLE);
    // 等待DMA1_Stream4可配置
    while (DMA_GetCmdStatus(WM8960_I2Sx_TX_DMA_STREAM) != DISABLE); 
    WM8960_I2Sx_TX_DMA_STREAM->M0AR = (uint32_t)buffer;
    WM8960_I2Sx_TX_DMA_STREAM->NDTR = size;
    DMA_Cmd(WM8960_I2Sx_TX_DMA_STREAM,ENABLE);
}

//开启DMA TX传输,开始播放    
void WM8960_I2S_Play_Start(void)
{         
    DMA_Cmd(WM8960_I2Sx_TX_DMA_STREAM,ENABLE);
}

//关闭DMA TX传输,结束播放
void WM8960_I2S_Play_Stop(void)
{         
    DMA_Cmd(WM8960_I2Sx_TX_DMA_STREAM,DISABLE);
}

// DMA传输中断函数
void WM8960_I2Sx_TX_DMA_STREAM_IRQFUN(void)
{
    if (DMA_GetITStatus(WM8960_I2Sx_TX_DMA_STREAM, WM8960_I2Sx_TX_DMA_IT_HTIF) == SET) // DMA传输过半标志
    {
        // 清DMA传输过半标志
        DMA_ClearITPendingBit(WM8960_I2Sx_TX_DMA_STREAM, WM8960_I2Sx_TX_DMA_IT_HTIF); 
        I2S_Flag = I2S_Half_Callback; // 过半传输
    }
    else if(DMA_GetITStatus(WM8960_I2Sx_TX_DMA_STREAM, WM8960_I2Sx_TX_DMA_IT_TCIF) == SET)
    {
        // 清DMA传输中断标志
        DMA_ClearITPendingBit(WM8960_I2Sx_TX_DMA_STREAM, WM8960_I2Sx_TX_DMA_IT_TCIF); 
        I2S_Flag = I2S_Callback;
        if(WAV_LastData >= WAV_BUFFER_SIZE)  {
            WM8960_I2S_DMA_Config((uint16_t *)WAV_Buffer, WAV_BUFFER_SIZE/2);
        }
        else  {
            Play_Flag = 0;
            End_Flag = 1;
        }
    }

}

/*************************************************************************** */



uint8_t WM8960_Write_Reg(uint8_t reg, uint16_t dat)  {
  
  uint8_t res,I2C_Data[2];
  
  I2C_Data[0] = (reg<<1)|((uint8_t)((dat>>8)&0x0001));  //RegAddr
  I2C_Data[1] = (uint8_t)(dat&0x00FF);                  //RegValue
  
  res = i2c_send_mutlibyte(WM8960_W_ADDR,I2C_Data,2);
  if(res == 2)
    WM8960_REG_VAL[reg] = dat;
  
  return res;
}

uint16_t WM8960_Read_Reg(uint8_t reg)
{
    return WM8960_REG_VAL[reg];
}


/**
 * WM8960_WS -> PB12/I2S2_WS
 * WM8960_BCLK -> PB13/I2S2_CK
 * WM8960_SD -> PB15/I2S2_SD
 */
/* Enable GPIO clock */
void WM8960_I2S_GPIO_Config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;

    RCC_AHB1PeriphClockCmd(WM8960_WS_GPIO_CLK | WM8960_BCLK_GPIO_CLK |
                               WM8960_SD_GPIO_CLK,
                           ENABLE);

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

    GPIO_InitStructure.GPIO_Pin = WM8960_WS_PIN;
    GPIO_Init(WM8960_WS_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = WM8960_BCLK_PIN;
    GPIO_Init(WM8960_BCLK_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.GPIO_Pin = WM8960_SD_PIN;
    GPIO_Init(WM8960_SD_PORT, &GPIO_InitStructure);

    /* Connect pins to I2S peripheral  */
    GPIO_PinAFConfig(WM8960_WS_PORT, WM8960_WS_SOURCE, WM8960_WS_AF);
    GPIO_PinAFConfig(WM8960_BCLK_PORT, WM8960_BCLK_SOURCE, WM8960_BCLK_AF);
    GPIO_PinAFConfig(WM8960_SD_PORT, WM8960_SD_SOURCE, WM8960_SD_AF);
}

// 配置I2S模式
void WM8960_I2Sx_Mode_Config(const uint16_t _usStandard, const uint16_t _usWordLen, const uint32_t _usAudioFreq)
{
    I2S_InitTypeDef I2S_InitStructure;

    uint32_t n = 0;
    FlagStatus status = RESET;

    /**
     *    For I2S mode, make sure that either:
     *        - I2S PLL is configured using the functions RCC_I2SCLKConfig(RCC_I2S2CLKSource_PLLI2S),
     *        RCC_PLLI2SCmd(ENABLE) and RCC_GetFlagStatus(RCC_FLAG_PLLI2SRDY).
     */
    RCC_I2SCLKConfig(RCC_I2S2CLKSource_PLLI2S);
    for (n = 0; n < 500; n++)
    {
        status = RCC_GetFlagStatus(RCC_FLAG_PLLI2SRDY);
        if (status == 1)
            break;
    }
    
    /* 打开 I2S2 APB1 时钟 */
    RCC_APB1PeriphClockCmd(WM8960_CLK, ENABLE);

    /* 复位 SPI2 外设到缺省状态 */
    SPI_I2S_DeInit(WM8960_I2Sx_SPI);

    /* I2S2 外设配置 */
    /* 配置I2S工作模式 */
    I2S_InitStructure.I2S_Mode = I2S_Mode_MasterTx; // 主机发送
    /* 接口标准 */
    I2S_InitStructure.I2S_Standard = _usStandard; // 飞利浦标准
    /* 数据格式，16bit */
    I2S_InitStructure.I2S_DataFormat = _usWordLen; // 数据字长
    /* BCLK空闲电平 */
    I2S_InitStructure.I2S_CPOL = I2S_CPOL_Low;
    I2S_Init(WM8960_I2Sx_SPI, &I2S_InitStructure);
    
    
    for(n = 0; n < (sizeof(I2S_PSC)/sizeof(I2S_PSC[0])); n++)
    {
        if(I2S_PSC[n][0] == _usAudioFreq/10)
            break;
    }
    // 配置采样时钟
    RCC_PLLI2SCmd(DISABLE);
    RCC_PLLI2SConfig(I2S_PSC[n][1],I2S_PSC[n][2]);
    RCC_PLLI2SCmd(ENABLE);
    SPI2->I2SPR |= 1 << 9;  // 使能MCLK
    SPI2->I2SPR &= ~(0xFF << 0);
    SPI2->I2SPR |= I2S_PSC[n][3] << 0;
    SPI2->I2SPR &= ~(1 << 8);
    SPI2->I2SPR |= I2S_PSC[n][4] << 8;
    
    /* 使能 SPI2/I2S2 外设 */
    I2S_Cmd(WM8960_I2Sx_SPI, ENABLE);
    // SPI2 TX DMA请求使能
    SPI_I2S_DMACmd(WM8960_I2Sx_SPI, SPI_I2S_DMAReq_Tx, ENABLE);
}

// 配置DMA发送
void WM8960_I2Sx_TX_DMA_Init(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    DMA_InitTypeDef DMA_InitStructure;
    // DMA1时钟使能
    RCC_AHB1PeriphClockCmd(WM8960_I2Sx_DMA_CLK, ENABLE); 

    DMA_DeInit(WM8960_I2Sx_TX_DMA_STREAM);
    while (DMA_GetCmdStatus(WM8960_I2Sx_TX_DMA_STREAM) != DISABLE)
    {
    } // 等待DMA1_Stream4可配置

    DMA_ClearITPendingBit(WM8960_I2Sx_TX_DMA_STREAM, DMA_IT_FEIF4 | DMA_IT_DMEIF4 | DMA_IT_TEIF4 | DMA_IT_HTIF4 | DMA_IT_TCIF4); // 清空DMA1_Stream4上所有中断标志

    /* 配置 DMA Stream */
    DMA_InitStructure.DMA_Channel = WM8960_I2Sx_TX_DMA_CHANNEL;                 // 通道0 SPIx_TX通道
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&WM8960_I2Sx_SPI->DR;  // 外设地址为:(u32)&SPI2->DR
    DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)0; // 后续指定缓冲区地址                  
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;                     // 存储器到外设模式
    DMA_InitStructure.DMA_BufferSize = 0; // 数据量，后续指定                                     
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;            // 外设非增量模式
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                     // 存储器增量模式
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外设数据长度:16位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;         // 存储器数据长度：16位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                             // 使用循环模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;                         // 高优先级
    DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;                      // 不使用FIFO模式
    DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
    DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;         // 外设突发单次传输
    DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // 存储器突发单次传输
    DMA_Init(WM8960_I2Sx_TX_DMA_STREAM, &DMA_InitStructure);            // 初始化DMA Stream

    // 开启传输完成中断和过半传输完成中断
    DMA_ITConfig(WM8960_I2Sx_TX_DMA_STREAM, DMA_IT_TC, ENABLE); 
    DMA_ITConfig(WM8960_I2Sx_TX_DMA_STREAM, DMA_IT_HT, ENABLE); 

    DMA_Cmd(WM8960_I2Sx_TX_DMA_STREAM, DISABLE);
    NVIC_InitStructure.NVIC_IRQChannel = WM8960_I2Sx_TX_DMA_STREAM_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;        // 子优先级2
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           // 使能外部中断通道
    NVIC_Init(&NVIC_InitStructure);                           // 配置
}

